package com.example.PLDroidMediaStreaming;

import android.content.Context;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.NonNull;

import com.qiniu.pili.droid.streaming.AVCodecType;
import com.qiniu.pili.droid.streaming.AudioSourceCallback;
import com.qiniu.pili.droid.streaming.CameraStreamingSetting;
import com.qiniu.pili.droid.streaming.MediaStreamingManager;
import com.qiniu.pili.droid.streaming.StreamStatusCallback;
import com.qiniu.pili.droid.streaming.StreamingEnv;
import com.qiniu.pili.droid.streaming.StreamingProfile;
import com.qiniu.pili.droid.streaming.StreamingSessionListener;
import com.qiniu.pili.droid.streaming.StreamingState;
import com.qiniu.pili.droid.streaming.StreamingStateChangedListener;

import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.StandardMessageCodec;

/** PLDroidMediaStreamingPlugin */
public class PLDroidMediaStreamingPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler ,
        StreamingStateChangedListener, StreamStatusCallback, AudioSourceCallback, StreamingSessionListener

{
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private MethodChannel channel;
  private EventChannel eventChannel;


  private HashMap<String, PLDroidMediaStreamingRenderView> mRendererViews;
  private Handler mEventHandler = new Handler(Looper.getMainLooper());
  private EventChannel.EventSink sink;
  private static String TAG = "PLDroidMediaStreamingPlugin";

  private MediaStreamingManager mMediaStreamingManager;
  private StreamingProfile mProfile;
  private CameraStreamingSetting mCameraSetting;
  private FlutterPluginBinding mFlutterPluginBinding;

  void addView(PLDroidMediaStreamingRenderView view, int id) {
    mRendererViews.put("" + id, view);
  }

  private void removeView(int id) {
      mRendererViews.remove("" + id);
  }

  private PLDroidMediaStreamingRenderView getView(int id) {
      return mRendererViews.get("" + id);
  }

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    mFlutterPluginBinding = flutterPluginBinding;

    channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "PLDroidMediaStreaming");
    channel.setMethodCallHandler(this);

    eventChannel = new EventChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "PLDroidMediaStreamingEventChannel");
    eventChannel.setStreamHandler(this);

    this.mRendererViews = new HashMap<>();

    PLDroidMediaStreamingRenderViewFactory fac = new PLDroidMediaStreamingRenderViewFactory(StandardMessageCodec.INSTANCE, this);

    flutterPluginBinding.getPlatformViewRegistry().registerViewFactory("PLDroidMediaStreamingRenderView", fac);

    StreamingEnv.init(flutterPluginBinding.getApplicationContext());
  }

  // This static function is optional and equivalent to onAttachedToEngine. It supports the old
  // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
  // plugin registration via this function while apps migrate to use the new Android APIs
  // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
  //
  // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
  // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
  // depending on the user's project. onAttachedToEngine or registerWith must both be defined
  // in the same class.
  public static void registerWith() {

  }


  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    Context context = mFlutterPluginBinding.getApplicationContext();
    switch (call.method) {
      case "init": {
        try {
          StreamingEnv.init(context);
          result.success(null);
        } catch (Exception e) {
          throw new RuntimeException("init error \n");
        }
        break;
      }
      case "setStreamingProfile":
        final String publishURLFromServer = call.argument("publishURL");
        try {
          mProfile = new StreamingProfile();
          mProfile.setVideoQuality(StreamingProfile.VIDEO_QUALITY_HIGH1)
                  .setAudioQuality(StreamingProfile.AUDIO_QUALITY_MEDIUM2)
                  .setEncodingSizeLevel(StreamingProfile.VIDEO_ENCODING_HEIGHT_480)
                  .setEncoderRCMode(StreamingProfile.EncoderRCModes.QUALITY_PRIORITY)
                  .setPublishUrl(publishURLFromServer);
          result.success(null);
        } catch (URISyntaxException exception) {
          throw new RuntimeException("setStreamingProfile error \n");
        }

        break;
      case "setCameraStreamingSetting":
        //preview setting
        mCameraSetting = new CameraStreamingSetting();
        mCameraSetting.setCameraId(Camera.CameraInfo.CAMERA_FACING_BACK)
                .setContinuousFocusModeEnabled(true)
                .setCameraPrvSizeLevel(CameraStreamingSetting.PREVIEW_SIZE_LEVEL.MEDIUM)
                .setCameraPrvSizeRatio(CameraStreamingSetting.PREVIEW_SIZE_RATIO.RATIO_16_9);
        result.success(null);
        break;
      case "removeLocalView": {
        int localViewId = call.argument("viewId");
        removeView(localViewId);
        result.success(null);
      }
        break;
      case "setLogLevel": {
        int logLevel = call.argument("level");
        StreamingEnv.setLogLevel(logLevel);
        result.success(null);
        break;
      }
      case "startStreaming":
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (mMediaStreamingManager != null) {
                    mMediaStreamingManager.startStreaming();
                }
            }
        }).start();
        result.success(null);
        break;
      case "stopStreaming":
        new Thread(new Runnable() {
          @Override
          public void run() {
            if (mMediaStreamingManager != null) {
              mMediaStreamingManager.stopStreaming();
            }
          }
        }).start();
        result.success(null);
        break;
      case "turnLightOff":
        new Thread(new Runnable() {
          @Override
          public void run() {
            if (mMediaStreamingManager != null) {
              mMediaStreamingManager.turnLightOff();
            }
          }
        }).start();
        result.success(null);
        break;
      case "turnLightOn":
        new Thread(new Runnable() {
          @Override
          public void run() {
            if (mMediaStreamingManager != null) {
              mMediaStreamingManager.turnLightOn();
            }
          }
        }).start();
        result.success(null);
        break;
      case "switchCamera":
        new Thread(new Runnable() {
          @Override
          public void run() {
            if (mMediaStreamingManager != null) {
              mMediaStreamingManager.switchCamera();
            }
          }
        }).start();
        result.success(null);
        break;
      case "setNativeLoggingEnabled":
        final boolean enabled = call.argument("enabled");
        new Thread(new Runnable() {
          @Override
          public void run() {
            if (mMediaStreamingManager != null) {
              mMediaStreamingManager.setNativeLoggingEnabled(enabled);
            }
          }
        }).start();
        result.success(null);
        break;
      case "pause":
        new Thread(new Runnable() {
          @Override
          public void run() {
              if (mMediaStreamingManager != null) {
                  mMediaStreamingManager.pause();
              }
          }
        }).start();
        result.success(null);
        break;
      case "destroy":
        mMediaStreamingManager.resume();
        result.success(null);
        break;
      default:
        result.notImplemented();
        break;
    }
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }

  @Override
  public void onAudioSourceAvailable(ByteBuffer byteBuffer, int i, long l, boolean b) {
    Log.i(TAG, "onAudioSourceAvailable");
  }

  @Override
  public void notifyStreamStatusChanged(StreamingProfile.StreamStatus streamStatus) {
    Log.i(TAG, "notifyStreamStatusChanged");
  }

  @Override
  public boolean onRecordAudioFailedHandled(int i) {
    Log.i(TAG, "onRecordAudioFailedHandled");
    return false;
  }

  @Override
  public boolean onRestartStreamingHandled(int i) {
    Log.i(TAG, "onRestartStreamingHandled");
    new Thread(new Runnable() {
        @Override
        public void run() {
            if (mMediaStreamingManager != null) {
                mMediaStreamingManager.startStreaming();
            }
        }
    }).start();
    return false;
  }

  @Override
  public Camera.Size onPreviewSizeSelected(List<Camera.Size> list) {
    return null;
  }

  @Override
  public int onPreviewFpsSelected(List<int[]> list) {
    return 0;
  }

  @Override
  public void onStateChanged(StreamingState streamingState, Object extra) {
    Log.e(TAG, "streamingState = " + streamingState + "extra = " + extra);
    final HashMap<String, String> map = new HashMap<>();
    switch (streamingState) {
      case PREPARING:
        Log.e(TAG, "PREPARING");
        map.put("state", "PREPARING");
        break;
      case READY:
        Log.e(TAG, "READY");
        map.put("state",  "READY");
        break;
      case CONNECTING:
        Log.e(TAG, "连接中");
        map.put("state",  "CONNECTING");
        break;
      case STREAMING:
        Log.e(TAG, "推流中");
        map.put("state",  "STREAMING");
        // The av packet had been sent.
        break;
      case SHUTDOWN:
        Log.e(TAG, "直播中断");
        map.put("state",  "SHUTDOWN");
        // The streaming had been finished.
        break;
      case IOERROR:
        // Network connect error.
        Log.e(TAG, "网络连接失败");
        map.put("state",  "IOERROR");
        break;
      case OPEN_CAMERA_FAIL:
        Log.e(TAG, "摄像头打开失败");
        map.put("state",  "OPEN_CAMERA_FAIL");
        // Failed to open camera.
        break;
      case DISCONNECTED:
        Log.e(TAG, "已经断开连接");
        map.put("state",  "DISCONNECTED");
        // The socket is broken while streaming
        break;
      case TORCH_INFO:
        map.put("state",  "TORCH_INFO");
        Log.e(TAG, "开启闪光灯");
        break;
      case UNAUTHORIZED_STREAMING_URL:
        map.put("state",  "UNAUTHORIZED_STREAMING_URL");
        Log.e(TAG, "未授权的流");
        break;
    }
    sendEvent("onStateChanged", map);
  }

  private void sendEvent(final String eventName, final HashMap map) {
      map.put("event", eventName);
      mEventHandler.post(new Runnable() {
          @Override
          public void run() {
              if (sink != null) {
                  sink.success(map);
              }
          }
      });
  }

  @Override
  public void onListen(Object o, EventChannel.EventSink eventSink) {
      this.sink = eventSink;
  }

  @Override
  public void onCancel(Object o) {
      this.sink = null;
  }
}
